package client.model.twoThousandFortyEight;

import java.util.Observable;
import java.util.Random;
import java.util.Stack;

import client.model.Model;
import common.CloneMatrix;

public class Game2048Model extends Observable implements Model {
	private int[][] game2048Array;
	private int size;
	private int winning; //lost=1, win=2
	private int score;
	private int winningTile;
	private boolean unlimitedGameMode=false;
	
	private boolean movementMadeFlag=false;
	
	private Stack<int[][]> undoMoves=new Stack<int[][]>();
	private Stack<Integer> undoScore=new Stack<Integer>();
	
	public Game2048Model(int size, int winningTile) {
		this.size = size;
		this.game2048Array = new int[size][size];
		this.winningTile = winningTile;
	}
	
	public Game2048Model(int size, int winningTile, Stack<int[][]> moves, Stack<Integer> scores) {
		this.size = size;
		this.winningTile = winningTile;
		undoMoves = moves;
		undoScore = scores;
	}
	
	@Override
	public String getGameName() {
		return "2048";
	}
	
	@Override
	public void initGame() {
		//Initialize the array
		this.game2048Array=new int[size][size];
		for(int i=0; i<size; i++){
			for(int j=0; j<size; j++){
				game2048Array[i][j]=0;
			}
		}
		
		//Initialize score and winning
		score=0;
		winning=0;
		unlimitedGameMode=false;
		
		//Random position
		int[] firstPosition=randomPosition();
		int[] secondPosition=randomPosition();
		while(firstPosition[0]==secondPosition[0] && firstPosition[1]==secondPosition[1]){			
			secondPosition=randomPosition();
		}
		
		//Implement the position of the random numbers
		game2048Array[firstPosition[0]][firstPosition[1]]=randomNumber();
		game2048Array[secondPosition[0]][secondPosition[1]]=randomNumber();
		
		//Initialize undo queues
		undoMoves.clear();
		undoScore.clear();
		
		setChanged();
		notifyObservers();
	}
	
	@Override
	public void moveLeft() {
		int sumMoveScore=0;
		movementMadeFlag=false;
		
		//Backup current board for undo stack
		int[][] backupBoard = CloneMatrix.clone(game2048Array);
		
		//Scans columns and updates the tiles
		for(int row=0; row<size; row++){
			for(int column=0; column<(size-1); column++){				
				for(int z=column+1; z<=(size-1); z++){
					if(game2048Array[row][z]!=0){
						if(compareTilesValue(game2048Array[row][column],game2048Array[row][z])){
							updateMove(row,column,row,z);
							sumMoveScore=game2048Array[row][column];
							movementMadeFlag=true;
						}
						break;
					}
				}
			}

			//Update the column
			for(int i=0;i<size;i++){
				if(game2048Array[row][i]==0){
					for(int j=i+1; j<size; j++){
						if(game2048Array[row][j]!=0){
							int temp=game2048Array[row][j];
							game2048Array[row][i]=temp;
							game2048Array[row][j]=0;
							if(i!=size-1){
								i++;
							}
							movementMadeFlag=true;
						}
					}		
				}
			}
		}

		int[] position= randomPosition();
		if(movementMadeFlag){
			//Update undo stacks
			undoMoves.push(backupBoard);
			undoScore.push(score);
			
			if(position!=null){				
				game2048Array[position[0]][position[1]]=randomNumber();
			}
		}
		
		updateScore(sumMoveScore);
		
		this.setChanged();
		this.notifyObservers();
	}
	
	@Override
	public void moveRight() {
		int sumMoveScore=0;
		movementMadeFlag=false;
		
		//Backup current board for undo stack
		int[][] backupBoard = CloneMatrix.clone(game2048Array);
		
		//Scans columns and updates the tiles
		for(int row=0; row<size; row++){
			for(int column=(size-1); column>0; column--){
				//int z=column-1;
				for(int z=column-1; z>=0; z--){
					if(game2048Array[row][z]!=0){
						if(compareTilesValue(game2048Array[row][column],game2048Array[row][z])){
							updateMove(row,column,row,z);
							sumMoveScore=game2048Array[row][column];
							movementMadeFlag=true;
						}
						break;
					}
				}
			}
			
			//Update the column
			for(int i=(size-1);i>0;i--){
				if(game2048Array[row][i]==0){
					for(int j=i-1; j>=0; j--){
						if(game2048Array[row][j]!=0){
							int temp=game2048Array[row][j];
							game2048Array[row][i]=temp;
							game2048Array[row][j]=0;
							if(i!=0){
								i--;
							}
							movementMadeFlag=true;
						}
					}		
				}
			}
		}
		
		int[] position= randomPosition();
		if(movementMadeFlag){
			//Update undo stacks
			undoMoves.push(backupBoard);
			undoScore.push(score);
			
			if(position!=null){				
				game2048Array[position[0]][position[1]]=randomNumber();
			}
		}
		
		updateScore(sumMoveScore);
		
		this.setChanged();
		this.notifyObservers();
	}
	
	@Override
	public void moveUp() {
		int sumMoveScore=0;
		movementMadeFlag=false;
		
		//Backup current board for undo stack
		int[][] backupBoard = CloneMatrix.clone(game2048Array);
		
		//Scans rows and updates the tiles
		for(int column=0; column<size; column++){
			for(int row=0; row<(size-1); row++){
				for(int z=row+1; z<=(size-1); z++){
					if(game2048Array[z][column]!=0){
						if(compareTilesValue(game2048Array[row][column],game2048Array[z][column])){
							updateMove(row,column,z,column);
							sumMoveScore=game2048Array[row][column];
							movementMadeFlag=true;
						}
						break;
					}
				}
			}
			
			//Update the row
			for(int i=0;i<size;i++){
				if(game2048Array[i][column]==0){
					for(int j=i+1; j<size; j++){
						if(game2048Array[j][column]!=0){
							int temp=game2048Array[j][column];
							game2048Array[i][column]=temp;
							game2048Array[j][column]=0;
							if(i!=size-1){
								i++;
							}
							movementMadeFlag=true;
						}
					}		
				}
			}
		}

		int[] position= randomPosition();
		if(movementMadeFlag){
			//Update undo stacks
			undoMoves.push(backupBoard);
			undoScore.push(score);
			
			if(position!=null){				
				game2048Array[position[0]][position[1]]=randomNumber();
			}
		}
		
		updateScore(sumMoveScore);
		
		this.setChanged();
		this.notifyObservers();
	}
	
	@Override
	public void moveDown() {
		int sumMoveScore=0;
		movementMadeFlag=false;
		
		//Backup current board for undo stack
		int[][] backupBoard = CloneMatrix.clone(game2048Array);
		
		//Scans rows and updates the tiles
		for(int column=0; column<size; column++){
			for(int row=(size-1); row>0; row--){
				for(int z=row-1; z>=0; z--){
					if(game2048Array[z][column]!=0){
						if(compareTilesValue(game2048Array[row][column],game2048Array[z][column])){
							updateMove(row,column,z,column);
							sumMoveScore=game2048Array[row][column];
							movementMadeFlag=true;
						}
						break;
					}
				}
			}
			
			//Update the row
			for(int i=(size-1);i>0;i--){
				if(game2048Array[i][column]==0){
					for(int j=i-1; j>=0; j--){
						if(game2048Array[j][column]!=0){
							int temp=game2048Array[j][column];
							game2048Array[i][column]=temp;
							game2048Array[j][column]=0;
							if(i!=0){
								i--;
							}
							movementMadeFlag=true;
						}
					}		
				}
			}
		}
		
		int[] position= randomPosition();
		if(movementMadeFlag){
			//Update undo stacks
			undoMoves.push(backupBoard);
			undoScore.push(score);
			
			if(position!=null){				
				game2048Array[position[0]][position[1]]=randomNumber();
			}
		}
		
		updateScore(sumMoveScore);
		
		this.setChanged();
		this.notifyObservers();
	}

	@Override
	public void moveUpRight() {
		// Not needed in this game
	}

	@Override
	public void moveDownRight() {
		// Not needed in this game
	}

	@Override
	public void moveUpLeft() {
		// Not needed in this game
	}

	@Override
	public void moveDownLeft() {
		// Not needed in this game
	}
	
	private boolean compareTilesValue(int i, int j) {
		if (i == j) {
			if (unlimitedGameMode) {
				winning = 0;
			}
			else if (i+j == winningTile) {
				winning = 2;
			}
			
			return true;
		}
		
		return false;
	}
	
	@Override
	public void updateScore(int sum) {
		score += sum;
	}
	
	private void updateMove(int row1, int column1, int row2, int column2) {
		int temp=game2048Array[row1][column1];
		temp*=2;
		game2048Array[row1][column1]=temp;
		game2048Array[row2][column2]=0;
	}
	
	private int[] randomPosition() {
		int[] position={0,0};
		Random rand = new Random();
		position[0]=rand.nextInt(size);
		position[1]=rand.nextInt(size);
		int counter=0;
		
		//Check if the position is empty
		while(game2048Array[position[0]][position[1]]!=0){
			position[0]=rand.nextInt(size);
			position[1]=rand.nextInt(size);
			if(game2048Array[position[0]][position[1]]!=0){
				counter++;
				//If the random taking too long
				if(counter==Math.pow(size, 2)){
					for(int i=0; i<size; i++){
						for(int j=0; j<size; j++){
							if(game2048Array[i][j]==0){
								position[0]=i;
								position[1]=j;
								return position;
							}
						}
					}
					//Board is full check if any moves left
					if (!checkMoves()) {
						winning = 1;
					}
					return null;
				}
			}
		}
		
		return position;
	}
	
	private boolean checkMoves() {
		//Check if there is possible moves in any row
		for(int row=0; row<size; row++){
			for(int column=0; column<(size-1); column++){
				if(compareTilesValue(game2048Array[row][column],game2048Array[row][column+1])){
					return true;
				}
			}
		}
		
		//Check if there is possible moves in any columns
		for(int column=0; column<size; column++){
			for(int row=0; row<(size-1); row++){
				if(compareTilesValue(game2048Array[row][column],game2048Array[row+1][column])){
					return true;
				}
			}
		}
		
		return false;
	}

	private int randomNumber() {
		//90% for 2 and 10% for 4
		return Math.random() < 0.9 ? 2 : 4;
	}
	
	@Override
	public int getScore() {
		return score;
	}
	
	@Override
	public int[][] getBoard() {
		return game2048Array;
	}	
	
	//winning=0 - keep playing, winning=1 - game lost, winning=2 - won the game
	@Override
	public int getGameState() {
		return winning;
	}
	
	@Override
	public void undoMove() {
		if(!(undoMoves.isEmpty())){			
			game2048Array=undoMoves.pop();
			score=undoScore.pop();
			
			this.setChanged();
			this.notifyObservers();
		}
	}

	@Override
	public void userOption() {
		unlimitedGameMode=true;
	}

	@Override
	@SuppressWarnings("unchecked")
	public Stack<Integer> getUndoScoresStack() {
		Stack<Integer> scores = (Stack<Integer>)undoScore.clone();
		scores.push(score);

		return scores;
	}

	@Override
	@SuppressWarnings("unchecked")
	public Stack<int[][]> getUndoMovesStack() {
		Stack<int[][]> moves = (Stack<int[][]>)undoMoves.clone();
		moves.push(CloneMatrix.clone(game2048Array));

		return moves;
	}
}